package com.ustc.hewei.shopmodule.utils;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.api.R;
import com.ustc.hewei.commonmodule.dto.LogicExpireData;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.ustc.hewei.shopmodule.utils.Constants.CACHE_LOCK_KEY;
import static com.ustc.hewei.shopmodule.utils.Constants.LOCK_TTL;

/**
* @description: TODO
* @author hewei
* @date 2022/10/10 20:35
* @version 1.0
*/

@Component
public class ShopCache {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedissonClient redissonClient;
    private final ExecutorService executorService = ThreadPoolFactory.createDefaultThreadPool("rebuildCache");

    /*
     * @description: 基于redis实现的分布式锁
     * @author: hewei
     * @date: 2022/10/14 22:00
     **/
    public boolean tryLock(String key) {
        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, Thread.currentThread().getName(), LOCK_TTL, TimeUnit.SECONDS));
    }

    public void unlock(String key) {
        stringRedisTemplate.delete(key);
    }

    public <T, ID> T queryWithLogicExpire(String prefix, ID id, Class<T> clazz, Function<ID, T> dbFallBack, Long time, TimeUnit unit) {
        String key = prefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (json == null) {
            return null;
        }
        LogicExpireData<T> logicExpireData = JSONUtil.toBean(json, LogicExpireData.class);
        LocalDateTime logicTTL = logicExpireData.getLogicTTL();
        JSONObject data = (JSONObject)logicExpireData.getData();
        T t = JSONUtil.toBean(data, clazz);
        if (logicTTL.isAfter(LocalDateTime.now())) {
            return t;
        }
        String lockKey = CACHE_LOCK_KEY + id;
        RLock lock = redissonClient.getLock(lockKey);
        boolean isLock = lock.tryLock();
        // 做双重校验，防止有线程判断之后，其他线程完成重建
        if (isLock) {
            json = stringRedisTemplate.opsForValue().get(key);
            logicExpireData = JSONUtil.toBean(json, LogicExpireData.class);
            if (logicExpireData.getLogicTTL().isBefore(LocalDateTime.now())) {
                executorService.submit(() -> {
                    try {
                        T newData = dbFallBack.apply(id);
                        LogicExpireData<T> newLogicExpire = new LogicExpireData<>();
                        newLogicExpire.setData(newData).setLogicTTL(LocalDateTime.now()
                                .plusSeconds(unit.toSeconds(time)));
                        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(newLogicExpire));
                    } finally {
                        lock.unlock();
                    }
                });
            } else {
                lock.unlock();
            }
        }
        return t;
    }
}
